package br.edu.unifei.mestrado.greedy;

import java.util.Collection;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.partition.AbstractSubSet;

/**
 * Armazena os nós internos de um set. <br>
 * Armazena os nós da fronteira que estão ligados ao nós internos desse set. <br>
 * 
 * @author roberto
 * 
 */
public class SubSet extends AbstractSubSet{

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	/**
	 * Quantidade de arestas internas de cada vertice da fronteira. id_vertice | N value
	 */
	private ValueMap NMap = new ValueMap();

	/**
	 * Valor do diff para cada vertice da fronteira. Externo - Interno.
	 */
	private DiffMap diffMap = new DiffMap();

	// UTIL: não usa pq já guarda direto o diff, baseado no calculo de externo e interno
	// /**
	// * Quantidade de arestas externas para cada vertice da fronteira.
	// */
	// private ValueMap extEdges = new ValueMap();

	/**
	 * Vértices para cada diff.
	 * 
	 * set | diff | id_vertice | vertice
	 */
	// private Bucket bucket = null;

	public SubSet(int id) {
		super(id);
	}

	public Integer getMinVal() {
		return diffMap.getMinDiff();
	}

	public void removeNodeWithDiff(int diff, long node) {
		diffMap.removeNodeWithDiff(diff, node);
	}

	private void setDiff(int newDiff, Integer oldDiff, NodeWrapper vertice) {
		if (oldDiff == null) {
			diffMap.insertDiffForNode(newDiff, vertice.getId());
		} else {
			diffMap.updateDiffForNode(newDiff, oldDiff, vertice.getId());
		}
	}

	public Long getAnyVerticeWithDiff(int diff) {
		return diffMap.getVerticesWithDiff(diff).iterator().next();
	}

	/**
	 * Retrieves the N value of the vertex.
	 * 
	 * @see {@link NArray#getNArray(int, NodeWrapper, int)}
	 * 
	 * @param set
	 * @param vertice
	 * @return
	 */
	public int getN(NodeWrapper vertice) {
		return NMap.getValue(vertice);
	}

	public void incrementN(NodeWrapper vertice) {
		NMap.incrementN(vertice);
	}

	public void printDiff() {
		logger.debug("Diffs for set: " + getId() + ": {}", new Object() {
			@Override
			public String toString() {
				StringBuffer b = new StringBuffer();
				for (Integer diff : diffMap.getDiffList()) {
					b.append("	Diff " + (diff >= 0 ? " " : "") + diff + ": ");
					for (Long nodeId : diffMap.getVerticesWithDiff(diff)) {
						b.append(nodeId + ", ");
					}
				}
				return b.toString();
			}
		});
	}

	public void updateDiff(Collection<SubSet> subSets, Collection<NodeWrapper> frontierNodes) {
		SubSet baseSet_P = this;
		logger.debug("Atualizando diff para " + baseSet_P + " qtd nodes: " + frontierNodes.size());

		// for (NodeWrapper vertice : frontierNodes) {
		// baseSet_P.setExt(vertice, 0);// inicializa o ext com zero // TODO: colocar junto com o for debaixo
		// }
		for (NodeWrapper vertice : frontierNodes) {
			Integer oldDiff = baseSet_P.diffMap.getDiffOfNode(vertice.getId());
			int extSum = 0;
			int intSum = 0;
			for (SubSet set_J : subSets) {// loop com todos os sets, para saber se conta interno ou externo
				if (set_J.getId() != baseSet_P.getId()) {
					extSum += set_J.getN(vertice);// acumula os N externos
				} else {
					intSum += baseSet_P.getN(vertice);// acumula os N internos
				}
			}
			// não usa pq já guarda direto o diff, baseado no calculo de externo e interno
			// baseSet_P.setExt(vertice, extSum);
			int diff = extSum - intSum;

			// UTIL: precisa fazer um update no diff do node 2, para ele não ficar duplicado na estrutura do
			// diff.

			baseSet_P.setDiff(diff, oldDiff, vertice); // salva o diff para o vertices em relação ao set
		}
		this.printDiff();
	}

	public void removeNode(NodeWrapper node) {
		Integer diff = diffMap.getDiffOfNode(node.getId());
		if (diff != null) {
			this.removeNodeWithDiff(diff, node.getId());
		}
	}

	// public int getExt(NodeWrapper vertice) {
	// return extEdges.getValue(vertice);
	// }
	//
	// public void setExt(NodeWrapper vertice, int valor) {
	// extEdges.setValue(vertice, valor);
	// }

}
